﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

using Effects.Wave;

namespace Effects.GUI
{
    /// <summary>
    ///  Struct representing some number of adjecant samples by storing values of minimum and maximum samples.
    /// </summary>
    /// <remarks>
    ///  This struct is used for little trick to speed-up the visualization on ShowWave
    /// </remarks>
    /// <seealso cref="ShowWave"/>
    public struct MinMaxSamplesPerPixel
    {
        /// <summary>
        ///  Minimum value
        /// </summary>
        private double min;

        /// <summary>
        ///  Gets or sets the minimal value represented by the struct.
        /// </summary>
        public double Min
        {
            get { return min; }
            set { min = value; }
        }

        /// <summary>
        ///  Maximal value.
        /// </summary>
        private double max;

        /// <summary>
        ///  Gets or sets maximal value represented by the struct.
        /// </summary>
        public double Max
        {
            get { return max; }
            set { max = value; }
        }

        /// <summary>
        ///  Creates new instance representing given minimal and maximal values.
        /// </summary>
        /// <param name="min"> Minimal value to represent </param>
        /// <param name="max"> Maximal value to represent  </param>
        public MinMaxSamplesPerPixel(double min, double max)
        {
            this.min = min;
            this.max = max;
        }
    }

    // New implementetion of ShowWave class
    /// <summary>
    ///  Visualizes Wave class, enable zooming and srolling the content.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   This class is quite simple visualization of Wave class. The most straithforward approach to just map pixels and samples 1:1.
    ///   For every sample take one pixel of pisture's widtht and according to the value of the sample put pixel (scaled to the maximum height). (index of sample and x-coordinate of pixel would be equal)
    ///   This results into a pretty big bitmaps and consumes lots of space. This bitmap have to be scaled to reasonable dimension and that is quite time-consuming operation
    ///  </para>
    ///  <para>
    ///   But we can use a little trick. Represent every N samples with one pixel of width. We easily find minimum and maximum among every N samples and draw vertical line between [x,y1] and [x,y2].
    ///   x - is the number for which N-samples group we proccess and y1,y2 is minimum and maximum scaled to the height of the picture.
    ///  </para>
    ///  <para>
    ///   Simple but still time-consuming when we have to run through the whole file. To speed-up we realize that we represents every N samples with two values - minimum and maximum.
    ///   An when the view is zoomed out, this N is quite big (number of samples / picture width). So what about pre-computing minimum and maximum from every 100 (or so) samples and work with them instead of actual samples.
    ///   This is the main speed improvment we uses array of MinMaxSamplesPerPixel structs where we store minimum and maximum of every M samples. When 
    ///   we need to Render image that is zoomed-out enough we use this array. When user zooms-in we use actual samples (only small part of file is needed to be proccessed)
    ///  </para>
    ///  <para>
    ///   When users zooms-in, only part of waveform is shown, so user probably wants to scroll the content. That can be done by instance of HSrollBar. To keep code clear we decided not to manage this scroll bar inside ShowWave class.
    ///   It must be displayed separately and passed to the ScrollBar property. ShowWavePanel class provides this functionality. <see cref="ShowPanel"/>
    ///  </para>
    ///  <seealso cref="ShowPanel"/>
    /// </remarks>
    public class ShowWave:UserControl
    {
        /// <summary>
        ///  Index of first sample in shown interval.
        /// </summary>
        private int begin;

        /// <summary>
        ///  Position of cursor as a sample index.
        /// </summary>
        private int cursor;

        /// <summary>
        ///  Index of last sample in shown interval.
        /// </summary>
        private int end;

        /// <summary>
        ///  Height in pixel per one channel.
        /// </summary>
        private int channelHeigth;

        /// <summary>
        ///  Gets or sets heigth of one channel (in pixels).
        /// </summary>
        private int ChannelHeigth
        {
            get { return channelHeigth; }
            set
            {
                channelHeigth = value;
                if (wav != null)
                {
                    // adjust height
                    this.Height = (channelHeigth + channelSpace) * wav.FmtChunk.Channels;
                }
                else
                    // adjust height
                    this.Height = channelHeigth;
            }
        }

        /// <summary>
        ///  Height of space that separeteas adjecant channels.
        /// </summary>
        private int channelSpace;

        
        /// <summary>
        ///  Event raised when Height of the Control is changed.
        /// </summary>
        public event ShowWaveHeightChangedDelegate HeightChanged;

        /// <summary>
        ///  Brush  used to paint cursor.
        /// </summary>
        private Brush cursorBrush;

        /// <summary>
        ///  Color used to create channel separator brushes
        /// </summary>
        private Color separatorColor;
        /// <summary>
        ///  Gets or sets the color of separators between channels.
        /// </summary>
        public Color SeparatorColor
        {
            get { return separatorColor; }
            set { separatorColor = value;}
        }

        /// <summary>
        ///  HatchBrush used while wave is preparing
        /// </summary>
        private Brush loadingBrush;

        /// <summary>
        ///  locker
        /// </summary>
        private object locker;

        /// <summary>
        ///  Color of pen, that draws waveform
        /// </summary>
        protected Color penColor;

        /// <summary>
        ///  Gets or sets the color of the waveform. 
        /// </summary>
        public Color PenColor
        {
            get { return penColor; }
            set { penColor = value; }
        }

        /// <summary>
        ///  How many samples will be represented by one MinMaxSamplesPerPixel struct in values array
        /// </summary>
        private int samplesPerStruct;

        /// <summary>
        ///  HScrollBar that adjusts which part of wave is shown
        /// </summary>
        protected HScrollBar scBar;
        /// <summary>
        ///  Gets or sets the scroll bar that srollc content.
        /// </summary>
        /// <remarks>
        ///  Remember that given scroll bar won't be displeyed by ShowWave class and it's neede to be done elsewhere. 
        ///  Anyway ShowWave will handle scroll bar's events and will adjust it's value.
        /// </remarks>
        public HScrollBar ScrollBar
        {
            get { return scBar; }
            set
            {
                scBar = value;
                scBar.Scroll += new ScrollEventHandler(this.ScrollBarValue_Changed);

                if (wav == null)
                {
                    scBar.Enabled = false;
                }
                else
                {
                    // when wav present, enables scroll bar and, sets position
                    scBar.Enabled = true;
                    scBar.SmallChange = 1;
                    scBar.LargeChange = 1;
                    ScrollscBar();
                }
            }
        }

        /// <summary>
        ///  Player for checking play position.
        /// </summary>
        private AudioPlayer player;

        /// <summary>
        ///  Gets or sets the player that will be accessed to get playing position.
        /// </summary>
        /// <remarks>
        ///  This player should be the same that really plays something. Otherwise user woulndn't see what is being played. 
        /// </remarks>
        public AudioPlayer Player
        {
            get { return player; }
            set { player = value; }
        }

        // array of MinMaxSamples...
        /// <summary>
        ///  array of MinMaxSamples...
        /// </summary>
        /// <remarks>
        ///  This is the array used to speed up the rendering when small zooms are used.
        /// </remarks>
        private MinMaxSamplesPerPixel[,] values;
        
        /// <summary>
        ///  Instance of Wave that are shown.
        /// </summary>
        protected Wave.Wave wav;
        
        /// <summary>
        ///  Gets or sets the Wave being shown.
        /// </summary>
        public Wave.Wave Wav
        {
            get { return wav; }
            set
            {
                wav = value;
                samplesPerStruct = 100;
               
                // if given value is not null it is prepared and shown
                if (wav == null)
                {
                    Render();
                    Refresh();
                    BeginInvoke(SetHeigthInvoker);
                }
                else
                {
                    prepared = false;
                    begin = 0;
                    end = Convert.ToInt32(wav.SampleLength);
                    zoom = 1;
                    // prepare asynchronously
                    prepareWorker.RunWorkerAsync();
                }
                
            }
        }

        /// <summary>
        ///  Invokes <see cref="ShowWave.SetHeigth"/> method.
        /// </summary>
        private MethodInvoker SetHeigthInvoker;

        /// <summary>
        ///  Sets new haight of the component according to number of channels in displayed wave.
        /// </summary>
        private void SetHeigth()
        {
            int assumedHeight;
            if (wav == null)
                assumedHeight = channelHeigth + channelSpace;
            else
                assumedHeight = (channelHeigth + channelSpace) * wav.FmtChunk.Channels;

            if (assumedHeight != Height)
            {
                if (HeightChanged != null)
                {
                    ShowWaveHeightChangedEventAgrs eArgs = new ShowWaveHeightChangedEventAgrs(assumedHeight);
                    HeightChanged(this,eArgs);
                }
            }
        }

        /// <summary>
        ///  Current zoom.
        /// </summary>
        private int zoom;

        /// <summary>
        ///  Invokes <see cref="M:ShowWave.Refresh"/> method.
        /// </summary>
        private MethodInvoker RefreshInvoker;

        /// <summary>
        ///  Creates new Showwave that displays given Wave
        /// </summary>
        /// <param name="wav"> Wave to visualize </param>
        public ShowWave(Wave.Wave wav)
        {
            // fill variables
            samplesPerStruct = 100;
            zoom = 1;
            channelHeigth = 100;
            channelSpace = 12;
            locker = new object();

            // fille method invokers
            SetHeigthInvoker = new MethodInvoker(SetHeigth);
            RefreshInvoker = new MethodInvoker(Refresh);
            updateCursorInvoker = new MethodInvoker(UpdateCursor);

            InitializePrepareWorker();

            // set colors
            penColor = Color.Blue;
            cursorBrush = new SolidBrush(Color.Beige);
            loadingBrush = new SolidBrush(Color.FromArgb(192, 255, 255, 255));

            // set wav and prepare
            this.wav = wav;
            Prepare();
           

            // set double buffer style
            SetStyle(ControlStyles.AllPaintingInWmPaint |
                          ControlStyles.UserPaint |
                          ControlStyles.DoubleBuffer, true);

            Render();
            Refresh();
            
            // create timer
            timer=new System.Timers.Timer(100);
            timer.Elapsed +=new System.Timers.ElapsedEventHandler(this.Timer_tick);
        }

        /// <summary>
        ///  Overridden dispose method. Stops timer.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            timer.Stop();
            base.Dispose(disposing);
        }

        /// <summary>
        ///  Moves currently showed interval to begin at given newBegin value. Zoom remains the same.
        /// </summary>
        /// <param name="newBegin"> begin of the interval </param>
        private void MoveTo(int newBegin)
        {
            // if newBegin is not in current wav throw exception
            if (newBegin > wav.SampleLength)
                throw new ArgumentException("Given argument doesn't fit wav file being shown");

            
            int interval = end - begin;
            // newBegin may be too close to the end of wav, so it is moved little forward
            if (newBegin + interval > wav.SampleLength)
            {
                begin = Convert.ToInt32(wav.SampleLength - interval);
                end = Convert.ToInt32(wav.SampleLength);
            }
            else
            {
                begin = newBegin;
                end = begin + interval;
            }

            Render();
            Refresh();
        }

        /// <summary>
        ///  method wraps Refresh method, if called from GUI-thread just calls Refresh method, otherwise invokes RefreshInvoker
        /// </summary>
        private void RefreshMultiThread()
        {
            if (InvokeRequired)
                BeginInvoke(RefreshInvoker);
            else
                Refresh();
        }

        /// <summary>
        ///  Overridden OnMouseClick. Sets cursor and play position according to the click position.
        /// </summary>
        /// <param name="e"></param>
        protected override void  OnMouseClick(MouseEventArgs e)
        {
            if (wav != null && !loading && background!=null)
            {
                // count play position
                int interval = end - begin;
                double x = (double)e.X / Width;
                int playPosition = begin + Convert.ToInt32(x * interval);

                cursor = playPosition;

                if (player != null)
                    player.PlayPosition = playPosition;

                // background stays the same, only cursor changes position
                RenderCursor();
                Refresh();
            }
        }

        /// <summary>
        ///  Overridden OnMouseEnter. Gets focus to the ShowWave.
        /// </summary>
        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            Focus();
        }

        /// <summary>
        ///  Overridden Mouse wheel event, calls ZoomIn or ZoomOut methods
        /// </summary>
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            if (wav != null)
            {
                //base.OnMouseWheel(e);
                // if zoom has been over the control
                if (e.X > -1 && e.X < Width && e.Y > -1 && e.Y < Height)
                {
                    // check direction of mouse wheel move
                    double where = (double)e.X / Width;
                    if (e.Delta > 0)
                        ZoomIn(where);
                    else
                        ZoomOut();
                    /*
                    // count ScrollBar
                    if (usingSamples)
                        ScrollBarSamples();
                    else
                        ScrollBarStructs();*/
                }
            }
        }

        /// <summary>
        ///  Overridden OnPaint method, just displays rendered image. Double buffer is used
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            //base.OnPaint(e);
            Graphics g = e.Graphics;
            try
            {
                g.DrawImage(image, 0, 0);
            }
            catch (ArgumentNullException)
            {
                    Render();
                    Refresh();
            }
        }

        /// <summary>
        ///  Overridden OnResize. Calls Render to prepare double buffer. Zooms-out, if needed.
        /// </summary>
        /// <remarks>
        ///  Method zooms-out if shown interval is too short and user scales the width up.
        /// </remarks>
        /// <param name="e"></param>
        protected override void OnResize(EventArgs e)
        {
            SetHeigth();

            // Zooms out, if image is too wide
            if (end - begin < Width)
            {
                        ZoomOut();
            }
            Render();
            Refresh();
            //base.OnResize(e);
        }

        #region prepareWorker 
        /// <summary>
        ///  Prepares Waves in background thread.
        /// </summary>
        private BackgroundWorker prepareWorker;

        /// <summary>
        ///  Initializes prepareWorker.
        /// </summary>
        private void InitializePrepareWorker()
        {
            prepareWorker = new BackgroundWorker();
            prepareWorker.WorkerSupportsCancellation = true;
            prepareWorker.DoWork += new DoWorkEventHandler(prepareWorked_DoWork);
            prepareWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(prepareWorker_RunWorkerCompleted);
        }

        /// <summary>
        ///  DoWork method of prepareWorker. Time consuming preparation of wave. 
        /// </summary>
        private void prepareWorked_DoWork(object sender, DoWorkEventArgs e)
        {
            Prepare();
        }

        /// <summary>
        ///  PrepareWorker Completed method. Wave is prepared, it renders and refreshes the control.
        /// </summary>
        private void prepareWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // prepare completed - call what necesarry
            prepared = true;

            // enable and scroll scBar
            scBar.Enabled = true;
            ScrollscBar();

            if (PrepareFinished != null)
            {
                BeginInvoke(this.PrepareFinished);
            }

            SetHeigth();
            Render();
            Refresh();
        }

        #endregion


        #region prepare
        /// <summary>
        ///  True if wave is prepared, false if not.
        /// </summary>
        /// <remarks>
        ///  Quite importatnt member. While wave being prepared Render might be called, but values array is not valid at the moment, so it is not used.
        /// </remarks>
        private bool prepared;

        // prepares given Wave by filling the MinMaxSamplesPerPixel array
        /// <summary>
        ///  Prepares wave. Fills values array with minimum and maximum from every samplesPerStruct pixels.
        /// </summary>
        /// <remarks>
        ///  Method proccesses all samples in wav and fills values array. This operation is quite time consuming, therefore Prepare method is usually
        ///  called in background thread. <see cref="prepareWorker"/> It also renders empty image and refreshes to show users that program is doing something :)
        /// </remarks>
        protected void Prepare()
        {
            if (wav == null)
            {
                // no Wave given, just render empty
                Render();
            }
            else
            {
                // show to the user, that we are working
                this.loading = true;
                RenderEmpty();
                RefreshMultiThread();

                // Wave given, prepares array of MinMaxStructs
                int arrayLenght = Convert.ToInt32(Math.Ceiling(((double)wav.SampleLength) / samplesPerStruct));

                while (arrayLenght < Width)
                {
                    samplesPerStruct /= 2;
                    arrayLenght = Convert.ToInt32(Math.Ceiling(((double)wav.SampleLength) / samplesPerStruct));
                }

                values = new MinMaxSamplesPerPixel[wav.FmtChunk.Channels,arrayLenght];

                //now fill values array
                for (int channel = 0; channel < wav.FmtChunk.Channels; channel++)
                {
                    int valuesIndex = 0;
                    int count = 0;

                    double max = double.MinValue;
                    double min = double.MaxValue;

                    // Get min- and max- value for every samplesPerStruct samples
                    foreach (double sample in wav.GetSampleDoubleEnumerator(channel))
                    {
                        max = Math.Max(max, sample);
                        min = Math.Min(min, sample);
                        count++;

                        if (count == samplesPerStruct)
                        {
                            values[channel,valuesIndex] = new MinMaxSamplesPerPixel(min, max);
                            min = double.MaxValue;
                            max = double.MinValue;

                            count = 0;
                            valuesIndex++;
                        }
                    }

                    // creating last struct
                    if (valuesIndex < arrayLenght)
                    {
                        values[channel,valuesIndex] = new MinMaxSamplesPerPixel(min, max);
                    }

                    begin = 0;
                    end = Convert.ToInt32(wav.SampleLength);
                    loading = false;
                }
            }
        }

        /// <summary>
        ///  Delegate for PrepareFinished event.
        /// </summary>
        public delegate void PrepareFinishedDelegate();

        /// <summary>
        ///  Raised when prepare operation of wav is completed.
        /// </summary>
        public event PrepareFinishedDelegate PrepareFinished;
        #endregion

        /// <summary>
        ///  Converts number of samples to cursor position on control.
        /// </summary>
        /// <param name="cursor"> Sample index where cursor should be. </param>
        /// <returns> Position of cursor on the control. </returns>
        private int CursorPosition(int cursor)
        {
            return Convert.ToInt32(Width * ((double)(cursor - begin) / (end - begin)));
        }

        /// <summary>
        ///  Indicates whether display loading message.
        /// </summary>
        private bool loading;

        #region render
        //bitmap used as a background. In fact is final image with cursor missing
        /// <summary>
        ///  Bitmap used as a background.
        /// </summary>
        /// <remarks>
        ///  In fact is final image with cursor missing. It is used to speed the rendering up. Mainly when rendering we need just move the cursor and background remains the same.
        /// </remarks>
        private Bitmap background;

        /// <summary>
        ///  Bitmap used for double-buffer.
        /// </summary>
        private Bitmap image;

        /// <summary>
        ///  Renders image.
        /// </summary>
        /// <remarks>
        /// <para>
        ///  This method renders image that is later drawed in OnPaint method. In fact it doesn't actually render, but calls appropriate methods
        ///  </para>
        ///  <para>
        ///   Rendering process is a bit more complicated. Rendering itself is done by RenderBackground method. <see cref="RenderBackground"/> This method uses data in 
        ///   imageMinMax array. This array is filled by RenderStructs <see cref="RenderStructs"/> or RenderSamples <see cref="RenderSamples"/> methods. At last RenderEmpty <see cref="RenderEmpty"/> method
        ///  </para>
        /// </remarks>
        private void Render()
        {
            if (wav == null || prepared == false)
                RenderEmpty();
            else
            {
                // if zoom is too close render using samples, otherwise render using structs prepared in values
                if (SampleToStruct(end-begin) < Width)
                    RenderSamples();
                else
                    RenderStructs();
                
                RenderBackground();
                RenderCursor();
            }

        }

        /// <summary>
        ///  Renders background using imageMinMax array
        /// </summary>
        /// <seealso cref="Render"/>
        private void RenderBackground()
        {
            // create new image
            Bitmap newImage = new Bitmap(Width, Height);
            Graphics g = Graphics.FromImage(newImage);

            int halfHeight = channelHeigth/ 2;
            int halfSpace = channelSpace / 2;
            // fill background
            g.FillRectangle(new SolidBrush(this.BackColor), 0, 0, Width, Height);

            Pen p = new Pen(penColor);

            for (int channel = 0; channel < wav.FmtChunk.Channels; channel++)
            {
                int heightOffset = halfSpace + channel * (channelHeigth+channelSpace);
                Brush separatorBrush1st = new System.Drawing.Drawing2D.LinearGradientBrush(new Point(0, heightOffset-channelSpace), new Point(0, heightOffset-halfSpace), BackColor, separatorColor);
                Brush separatorBrush2nd = new System.Drawing.Drawing2D.LinearGradientBrush(new Point(0, heightOffset-halfSpace), new Point(0, heightOffset), separatorColor, BackColor);

                g.FillRectangle(separatorBrush1st, 0, heightOffset - channelSpace, Width, halfSpace);
                g.FillRectangle(separatorBrush2nd, 0, heightOffset - halfSpace, Width, halfSpace);

                // draw wave form
                for (int x = 0; x < Width; x++)
                {
                    int y1, y2;

                    try
                    {
                        y1 = Convert.ToInt32(heightOffset + halfHeight - halfHeight * imageMinMax[channel, x].Max);
                        y2 = Convert.ToInt32(heightOffset + halfHeight - halfHeight * imageMinMax[channel, x].Min);
                    }
                    catch (OverflowException)
                    {
                        y1 = 0;
                        y2 = 0;
                    }

                    // if y1 == y2 it is needed to draw small square instead od line
                    if (y1 == y2)
                        g.DrawRectangle(p, x, y1, 1, 1);
                    else
                        g.DrawLine(p, x, y1, x, y2);
                }
                
                g.DrawLine(Pens.Black, 0, heightOffset + halfHeight, Width, heightOffset + halfHeight);
            }

            Brush lastSeparatorBrush1st = new System.Drawing.Drawing2D.LinearGradientBrush(new Point(0, Height - halfSpace), new Point(0, Height), BackColor, separatorColor);
            g.FillRectangle(lastSeparatorBrush1st, 0, Height - halfSpace, Width, halfSpace);

            background = new Bitmap(newImage);
        }

        /// <summary>
        ///  Draws cursor to the bacground and replaces image
        /// </summary>
        private void RenderCursor()
        {
            Bitmap newImage = new Bitmap(background);
            int cursorX;

            // count X of a crusor position
            if (SampleToStruct(end - begin) < Width)
            {
                // using samples
                int samplesPerPixel = (end - begin) / Width;
                cursorX = player.PlayPosition - begin;
                cursorX /= samplesPerPixel;
            }
            else
            {
                // using structs

                int beginStruct = begin / samplesPerStruct;
                int endStruct = end / samplesPerStruct;
                int structsPerPixel = (endStruct - beginStruct + Width - 1 ) / Width;

                cursorX = player.PlayPosition - begin;
                cursorX /= samplesPerStruct;
                cursorX /= structsPerPixel;
            }

            if (cursorX > -1 && cursorX < Width)
            {
                Graphics g = Graphics.FromImage(newImage);

                g.FillRectangle(cursorBrush, cursorX - 2, 0, 4, Height);
            }
            
            image = newImage;
        }

        /// <summary>
        ///  Renders empty waveform,, in case that null is to be displayed.
        /// </summary>
        /// <seealso cref="Render"/>
        private void RenderEmpty()
        {
            Bitmap newImage = new Bitmap(Width, Height);
            Graphics g = Graphics.FromImage(newImage);

            int halfHeight = this.Height / 2;
            g.FillRectangle(new SolidBrush(this.BackColor), 0, 0, Width, Height);

            g.DrawLine(Pens.Black, 0, halfHeight, Width, halfHeight);
            if (loading)
            {
                g.FillRectangle(loadingBrush, 0, 0, Width, Height);
                g.DrawString("Preparing file", new Font(FontFamily.GenericMonospace, channelHeigth / 4), new SolidBrush(penColor), Width / 4, Height / 2 - channelHeigth /8);
            }

            image = newImage;
        }

        /// <summary>
        ///  Array used to store output of render. It stores output of RenderSamples or RenderStructs methods
        /// </summary>
        /// <seealso cref="Render"/>
        private MinMaxSamplesPerPixel[,] imageMinMax;

        /// <summary>
        ///  Fills imageMinMax array using real samples.
        /// </summary>
        /// <seealso cref="Render"/>
        private void RenderSamples()
        {
            MinMaxSamplesPerPixel[,] newArray = new MinMaxSamplesPerPixel[wav.FmtChunk.Channels,this.Width];

            int samplesPerPixel = (end - begin) / Width;
            
            for (int channel = 0; channel < wav.FmtChunk.Channels; channel++)
            {
                int sampleIndex = begin;

                for (int x = 0; x < this.Width; x++)
                {
                    double min = double.MaxValue;
                    double max = double.MinValue;
                    for (int i = 0; i < samplesPerPixel; i++)
                    {
                        min = Math.Min(min, wav.getSample(channel, sampleIndex));
                        max = Math.Max(max, wav.getSample(channel, sampleIndex));

                        sampleIndex++;
                    }
                    newArray[channel,x] = new MinMaxSamplesPerPixel(min, max);

                }
            }

            imageMinMax = newArray;
        }

        /// <summary>
        ///  Fills imageMinMax array using MinMax structs in values array.
        /// </summary>
        /// <seealso cref="Render"/>
        private void RenderStructs()
        {
            MinMaxSamplesPerPixel[,] newArray = new MinMaxSamplesPerPixel[wav.FmtChunk.Channels,Width];

            //int structsPerPixel=values.Length/this.Width;
            int structsPerPixel = (SampleToStruct(end) - SampleToStruct(begin) + Width - 1) / this.Width;
            int valuesMaxIndex = values.Length / wav.FmtChunk.Channels;

            for (int channel = 0; channel < wav.FmtChunk.Channels; channel++)
            {
                int valuesIndex = SampleToStruct(begin);
                double min=0;
                double max=0;
                int x=0;

                try
                {

                    for (x = 0; x < this.Width && valuesIndex < valuesMaxIndex; x++)
                    {
                        min = double.MaxValue;
                        max = double.MinValue;

                        for (int i = 0; i < structsPerPixel && valuesIndex < valuesMaxIndex; i++)
                        {
                            min = Math.Min(min, values[channel, valuesIndex].Min);
                            max = Math.Max(max, values[channel, valuesIndex].Max);
                            valuesIndex++;
                        }

                        newArray[channel, x] = new MinMaxSamplesPerPixel(min, max);
                    }
                }
                catch (Exception)
                {
                    // do nothing
                }

                newArray[channel, Width - 1] = new MinMaxSamplesPerPixel(min, max);

                while (++x < Width)
                {
                    newArray[channel, x] = new MinMaxSamplesPerPixel(0, 0);
                }

                
            }
            imageMinMax = newArray;
        }

        #endregion

        #region sampleStructConversions
        /// <summary>
        ///  Converts sample to the struct. In other words for given sample it counts index of struct that represents this sample
        /// </summary>
        /// <param name="sample"> index of sample </param>
        /// <returns> index of struct representing given sample</returns>
        private int SampleToStruct(int sample)
        {
            return sample / samplesPerStruct;
        }

        /// <summary>
        ///  Converts struct to sample. For given struct index, it returns index of its first sample 
        /// </summary>
        /// <param name="struc"> index of struct </param>
        /// <returns> index of sample that is first in given struct </returns>
        private int StructToSample(int struc)
        {
            return struc * samplesPerStruct;
        }
        #endregion


        #region scrollBar
        /// <summary>
        ///  Sets scroll bar position according to currently shown interval.
        /// </summary>
        /// <remarks>
        ///  Method not only sets current scroll bar's position but also adjust range of the scroll bar as needed.
        /// </remarks>
        private void ScrollscBar()
        {
            // how many positions 
            int positions = Convert.ToInt32( 100 * (wav.SampleLength / (end - begin)) - 99);
            // current position
            int current = Convert.ToInt32( positions * ((double) begin / wav.SampleLength ));
            scBar.Minimum = 0;
            if (positions == 0)
                scBar.Maximum = positions;
            else
                scBar.Maximum = positions-1;
            scBar.Value = current;

            scBar.SmallChange = 1;
            scBar.LargeChange = 1;
        }

        /// <summary>
        ///  Scroll bar event handler. Moves displayed part of waveform.
        /// </summary>
        private void ScrollBarValue_Changed(object sender, ScrollEventArgs e)
        {
            MoveTo(Convert.ToInt32(scBar.Value * ( wav.SampleLength / (scBar.Maximum+100))));
        }

        #endregion

        #region timer

        /// <summary>
        ///  Timer that controls refreshing of cursor
        /// </summary>
        /// <remarks>
        ///  With every tick timer checks current playing position of player <see cref="player"/> and moves cursor.
        /// </remarks>
        /// <seealso cref="Timer_tick"/>
        private System.Timers.Timer timer;

        /// <summary>
        /// Starts the timer.  
        /// </summary>
        /// <seealso cref="timer"/>
        public void StartTimer()
        {
            timer.Enabled = true;
        }

        /// <summary>
        ///  Stops the timer
        /// </summary>
        /// <seealso cref="timer"/>
        public void StopTimer()
        {
            timer.Enabled = false;
            Render();
            Refresh();
        }
        
          /// <summary>
        ///  Timer tick event handler
        /// </summary>
        /// <remarks>
        ///  Invokes cursor update.
        /// </remarks>
        private void Timer_tick(object sender, System.Timers.ElapsedEventArgs e)
        {
            if(!loading)
                BeginInvoke(updateCursorInvoker);
        }

        #endregion

        #region updatingCursor
        /// <summary>
        ///  Invoker for updating curosr position.
        /// </summary>
        /// <remarks>
        /// Method invoker is used becouse from .NET 2.0 it's forbidden to refresh component from other threads. So timer just invokes
        /// method stored in updateCursorInvoker.
        /// </remarks>
        MethodInvoker updateCursorInvoker;

        /// <summary>
        ///  Updates cursor according to the current player.PlayPosition
        /// </summary>
        private void UpdateCursor()
        {
            if (player != null)
            {
                int playPosition = player.PlayPosition;

                if (CursorPosition(cursor) != CursorPosition(playPosition))
                {
                    cursor = playPosition;

                    if (cursor > end)
                    {
                        MoveTo(cursor);
                        ScrollscBar();
                    }
                    else
                    {
                        RenderCursor();
                        Refresh();
                    }
                }
            }
        }
        #endregion

        #region zooming

        /// <summary>
        ///  Zoom in
        /// </summary>
        /// <remarks>
        ///  Method sets displayed interval to has half the current length. Middle of the interval is specified by X parameter.
        /// </remarks>
        /// <param name="X"> specifies middle of displayed interval after zooming </param>
        private void ZoomIn(double X)
        {
            int interval = end - begin;
            int interval2=interval/2;

            if (interval > 2* Width)
            {
                // we can zoom
                int newMiddle = begin + Convert.ToInt32(X * interval);

                if (newMiddle - (interval2) < begin)
                    end = begin + interval2;
                else if (newMiddle + (interval2) > end)
                    begin = end - interval2;
                else
                {
                    begin = newMiddle - interval2/2;
                    end = newMiddle + interval2/2;
                }

                zoom *= 2;
                ScrollscBar();
                
                Render();
                Refresh();
        
            }
        }

        /// <summary>
        ///  Zoom out
        /// </summary>
        /// <remarks>
        ///  Method sets new displayed interval. New one is two times longer than the old one. Middle of the interval stays the same, if it is possible.
        /// </remarks>
        private void ZoomOut()
        {
            // compute middle
            int middle = (begin + end) / 2;
            int interval = end - begin;

            if (zoom > 1)
            {
                if (middle - interval < 0)
                {
                    // middle of the interval is too close to the begin, begin is set to 0
                    begin = 0;
                    end = 2 * interval;
                }
                else if (middle + interval > wav.SampleLength)
                {
                    // middle of the interval is too close to the end, end is set to last sample
                    end = Convert.ToInt32(wav.SampleLength);
                    begin = end - 2 * interval;
                }
                else
                {
                    // everything is fine 
                    begin = middle - interval;
                    end = middle + interval;
                }

                zoom /= 2;
                ScrollscBar();

                Render();
                Refresh();
            }
        }
        #endregion
    }
}
